"""
Copyright (c) 2012, 2013 Regents of the University of California
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions 
are met:

 - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
OF THE POSSIBILITY OF SUCH DAMAGE.
"""

import smap.archiver.client
from dateutil import tz
import datetime
from calendar import timegm
from django.utils import timezone
from operator import itemgetter
from models import *
from django.db.models import Q
from math import exp, pow, sqrt
import time
import scipy.interpolate
import calendar
import logging
import numpy as np
from sets import Set
from math import exp, log
import types
from scipy.stats import scoreatpercentile
from scipy.stats import nanmedian
from olwidget.widgets import InfoMap
from django.utils import simplejson
import os
os.environ['MPLCONFIGDIR'] = '/tmp'
import pylab
from matplotlib import colors

global colormap 
colormap = ["#0000ff","#008000","#ff0000","#00bfbf","#bf00bf","#bfbf00","#404040","#0000ff","#008000","#ff0000","#00bfbf","#bf00bf","#bfbf00","#404040","#0000ff","#008000","#ff0000","#00bfbf","#bf00bf","#bfbf00","#404040","#0000ff","#008000","#ff0000","#00bfbf","#bf00bf","#bfbf00","#404040"]
global time_zone_utc
time_zone_utc = tz.gettz('UTC')
global dt_format
dt_format = '%Y-%m-%d %H:%M'
global symbol_choices
symbol_choices = ['circle','triangle','cross','square', 'diamond']

def userFilters(post_data):
    query = []
    #build second part of the query based on spatial filtering parameters from tree
    if len(post_data['Zones']) > 0:
        query.append('.filter(')
        for i in xrange(len(post_data['Zones'])):
            if i + 1 < len(post_data['Zones']):
                query.append('Q(zone__name=\'%s\') | ' % post_data['Zones'][i])
            else:
                query.append('Q(zone__name=\'%s\'))' % post_data['Zones'][i])
    if len(post_data['Orientations']) > 0:
        query.append('.filter(')
        for i in xrange(len(post_data['Orientations'])):
            if i + 1 < len(post_data['Orientations']):
                query.append('Q(zone__orientation=\'%s\') | ' % post_data['Orientations'][i])
            else:
                query.append('Q(zone__orientation=\'%s\'))' % post_data['Orientations'][i])
    if len(post_data['Zone Types']) > 0:
        query.append('.filter(')
        for i in xrange(len(post_data['Zone Types'])):
            if i + 1 < len(post_data['Zone Types']):
                query.append('Q(zone__zone_type=\'%s\') | ' % post_data['Zone Types'][i])
            else:
                query.append('Q(zone__zone_type=\'%s\'))' % post_data['Zone Types'][i])
    if len(post_data['Space Types']) > 0:
        query.append('.filter(')
        for i in xrange(len(post_data['Space Types'])):
            if i + 1 < len(post_data['Space Types']):
                query.append('Q(zone__space_type=\'%s\') | ' % post_data['Space Types'][i])
            else:
                query.append('Q(zone__space_type=\'%s\'))' % post_data['Space Types'][i])
    query.append('.distinct()')
    query = ''.join(query)
    return query

def deviceQuery(post_data):
    if len(post_data['Devices']) > 0:
        devicequery = []
        devicequery.append('.filter(')
        devicequeryTO = []
        devicequeryTO.append('.filter(')
        for i in xrange(len(post_data['Devices'])):
            if i + 1 < len(post_data['Devices']):
                devicequery.append('Q(name=\'%s\') | ' % post_data['Devices'][i])
                devicequeryTO.append('Q(device__name=\'%s\') | ' % post_data['Devices'][i])
            else:
                devicequery.append('Q(name=\'%s\'))' % post_data['Devices'][i])
                devicequeryTO.append('Q(device__name=\'%s\'))' % post_data['Devices'][i])
        devicequery = ''.join(devicequery)
        devicequeryTO = ''.join(devicequeryTO)
    else:
        devicequery = ''
        devicequeryTO = ''
    return [devicequery, devicequeryTO]

#function to build the chart data to send to flot for the stratification chart or strat_comfort chart
def buildRasCharts(test_objects, post_data, time_zone, deviceUuids, sensorHeights):
    chart_data = []
    labels = []
    series_colors = []
    symbols = []
    fp_ct = [0] 
    alert = False
    s = 0
    for i in xrange(len(test_objects)):
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=["Thermal comfort"])
        zone = zones[0]
        if s > len(symbol_choices) - 1:
            s = 0
        if i >= len(colormap):
            alert = True
            break
        color_s = colormap[i]
        smap_uuids = deviceUuids['Cart']
        if post_data['analysisType'] == 'stratComfort':
            smap_comfort_uuids = []
            for uuid in smap_uuids:
                if 3 < sensorHeights[uuid] < 70: #take only sensors in the occupied zone for the comfort calc
                    smap_comfort_uuids.append(uuid)
            
        end = int(calendar.timegm(test_objects[i].end_time.utctimetuple())) * 1000
        
        if post_data['analysisType'] == 'ras':
            strat = buildStrat(smap_uuids, end - 120000, end, sensorHeights, False)
            if len(strat) > 0:
                chart_data.append(strat)
                symbols.append('circle')
                labels.append(test_objects[i].end_time.astimezone(time_zone).strftime(dt_format) + ' ' + zone.name)
        else: #generate the points for the strat/comfort chart
            strat = buildStrat(smap_comfort_uuids, end - 120000, end, sensorHeights, True)
            if len(strat) > 0:
                chart_data.append(strat)
                symbols.append(symbol_choices[s])
                labels.append(test_objects[i].end_time.astimezone(time_zone).strftime(dt_format) + ' ' + zone.name)
        
        #check for fixed poles in the same zone that the test was taken and add those strats
        #needs fixing
        if post_data['stratDevice'] == 'cart_pole':
            if not 'aggregate' in post_data:
                series_colors.append(color_s)
            for device in fp_devices:
                fp_test_object = Test.objects.filter(device__name=str(device.name)).distinct()
                fp_zones = fp_test_object.zone.filter(zone_category__pmp_category__in=["Thermal comfort"])
                if zone.name == fp_zones[0].name:
                    fp_ct[0] += 1
                    smap_uuids = deviceUuids[str(device.name)]
                    if post_data['analysisType'] == 'stratComfort':
                        smap_comfort_uuids = []
                        for uuid in smap_uuids:
                            if 3 < sensorHeights[uuid] < 70:
                                smap_comfort_uuids.append(uuid)
                    labels.append(test_objects[i].end_time.astimezone(time_zone).strftime(dt_format) + ' ' + str(device.name))
                    if post_data['analysisType'] == 'ras':
                        chart_data.append(buildStrat(smap_uuids, end - 120000, end, sensorHeights, False))
                        if not 'aggregate' in post_data:
                            symbols.append('cross')
                    else: #strat_comfort
                        chart_data.append(buildStrat(smap_comfort_uuids, end - 120000, end, sensorHeights, True))
                        symbols.append(symbol_choices[s])
                    if not 'aggregate' in post_data:
                        series_colors.append(color_s)
        s += 1 #increment symbol counter
    #after looping through test_objects, aggregate
    if 'aggregate' in post_data:
        series_data = []
        if post_data['analysisType'] == 'ras':
            std_heights = [-10, 4, 10, 24, 48, 67, 84, 104, 152, 155] #this also includes pole heights
            for i in xrange(len(std_heights)): #loop over heights
                height_tsum = 0
                height_ct = 0
                for strat in chart_data: #loop over stratifications
                    for j in xrange(len(strat)): #loop over each point in the stratifications to find the matching height
                        if strat[j][1] == std_heights[i]:
                            height_tsum += strat[j][0]
                            height_ct += 1
                            break
                    
                if height_ct > 0:
                    height_tavg = height_tsum / height_ct
                    series_data.append([height_tavg,std_heights[i]])
        else: #stratcomfort
            tsum = 0
            stratsum = 0
            for pt in chart_data:
                try:
                    tsum += pt[0][0]
                    stratsum += pt[0][1]
                except:
                    tsum += 0
                    stratsum += 0
            if tsum > 0 and len(chart_data) > 0:
                series_data.append([tsum/len(chart_data), stratsum/len(chart_data)])
        
        return [series_data, series_colors, fp_ct[0]]
    else:
        return [chart_data, symbols, labels, series_colors, alert]

def buildComfortChart(test_objects, post_data, time_zone, deviceUuids):
    icmData = []
    seriesData = []
    wkd = post_data['wkd']
    for i in xrange(len(test_objects)):

        #get uuids for this device instance, get data based on device instance times
        #could send both uuids to resampleSmapData function, but right now without the ability to map original uuid to transformed uuid, hard to get them back
        #risk of unaligned data? should fix this to be one call to resampleSmapData....
        smap_uuids = deviceUuids[str(test_objects[i].device)]
        
        #these are catches that are eventually unnecessary as all ICMs should have anem and rh--but currently some do not
        no_rh = False
        no_anem = False
        try:
            rh_data = resampleSmapData(smap_uuids['rh'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
            rh_data = np.array(rh_data[0]['Readings'])
        except:
            no_rh = True
        try:
            anem_data = resampleSmapData(smap_uuids['anem'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
            anem_data = np.array(anem_data[0]['Readings'])
        except:
            no_anem = True
            
        if no_rh == False and len(rh_data) > 0: #only move forward if you have rh data (this really shouldn't be the case for the spline_top chart--lazy)
            if post_data['chart_type'] == 'comfort_tool':
                glb_data = resampleSmapData(smap_uuids['glb'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
                dbt_data = resampleSmapData(smap_uuids['dbt'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
                glb_data = np.array(glb_data[0]['Readings'])
                dbt_data = np.array(dbt_data[0]['Readings'])
            
            else: #spline_top
                top_data = resampleSmapDataTop(smap_uuids['glb'], smap_uuids['dbt'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
            
            if post_data['avg_day']:
                #run average day
                if post_data['chart_type'] == 'comfort_tool':
                    glb_data = avgDayComfort(glb_data, time_zone, wkd)
                    dbt_data = avgDayComfort(dbt_data, time_zone, wkd)
                else:
                    top_data = avgDayComfort(top_data, time_zone, wkd)
                rh_data = avgDayComfort(rh_data, time_zone, wkd)
                if not no_anem:
                    anem_data = avgDayComfort(anem_data, time_zone, wkd)
    
            #filter data
            if not post_data['ad']:
                sh = post_data['sh']
                eh = post_data['eh']
                if post_data['chart_type'] == 'comfort_tool':
                    glb_data = filterData(glb_data, time_zone, wkd, sh, eh)
                    dbt_data = filterData(dbt_data, time_zone, wkd, sh, eh)
                    rh_data = filterData(rh_data, time_zone, wkd, sh, eh)
                    if not no_anem:
                        anem_data = filterData(anem_data, time_zone, wkd, sh, eh)
                else: #include nulls for spline_top chart type
                    rh_data = filterData(rh_data, time_zone, wkd, sh, eh, True)
                    top_data = filterData(top_data, time_zone, wkd, sh, eh, True)
            
            #figure out the right zone to give it the right label
            zones = test_objects[i].zone.filter(zone_category__pmp_category__in=["Thermal comfort"])
            zone = zones[0]
                
            if post_data['chart_type'] == 'spline_top':
                smapData = top_data
                if len(smapData) > 0:
                    smapData = adjustTime(smapData, time_zone) #adjust to correct time_zone for highcharts
                    dataPoints = []
                    for elem in list(smapData):
                        try:
                            dataPoints.append([elem[0],round(elem[1], 1)])
                        except:
                            dataPoints.append([elem[0],None])
                    seriesData.append({'name': test_objects[i].device.name + ' ' + zone.name,'type': 'spline','data': dataPoints})
            else: #comfort tool
                icmObj = {}
                icmObj['glb'] = list(glb_data[:,1])
                icmObj['dbt'] = list(dbt_data[:,1])
                icmObj['label'] = test_objects[i].device.name + ' ' + zone.name
                icmObj['datetime'] = list(rh_data[:,0])
                icmObj['rh'] = list(rh_data[:,1])
                if test_objects[i].metabolic_rate != None:
                    icmObj['met'] = test_objects[i].metabolic_rate
                else:
                    icmObj['met'] = post_data['met']
                if test_objects[i].clothing_level != None:
                    icmObj['clo'] = test_objects[i].clothing_level
                else:
                    icmObj['clo'] = post_data['clo']
                if not no_anem:
                    icmObj['anem'] = list(anem_data[:,1])
                icmData.append(icmObj)
                
    if post_data['chart_type'] == 'spline_top':
        return seriesData
    if 'aggregate' in post_data:
        if len(icmData) > 0:
            glb_avg_str = ['map(np.mean,zip(']
            dbt_avg_str = ['map(np.mean,zip(']
            rh_avg_str = ['map(np.mean,zip(']
            anem_avg_str = ['map(np.mean,zip(']
            for i in xrange(len(icmData)):
                if i + 1 < len(icmData):
                    glb_avg_str.append('icmData[%i][\'glb\'],' % i)
                    dbt_avg_str.append('icmData[%i][\'dbt\'],' % i)
                    rh_avg_str.append('icmData[%i][\'rh\'],' % i)
                    if 'anem' in icmData[i]:
                        anem_avg_str.append('icmData[%i][\'anem\'],' % i)
                else:
                    glb_avg_str.append('icmData[%i][\'glb\']))' % i)
                    dbt_avg_str.append('icmData[%i][\'dbt\']))' % i)
                    rh_avg_str.append('icmData[%i][\'rh\']))' % i)
                    if 'anem' in icmData[i]:
                        anem_avg_str.append('icmData[%i][\'anem\']))' % i)
            
            glb_avg = eval(''.join(glb_avg_str))
            dbt_avg = eval(''.join(dbt_avg_str))
            rh_avg = eval(''.join(rh_avg_str))
            #check to make sure there is at least 1 anemometer
            if len(anem_avg_str) > 1:
                anem_avg = eval(''.join(anem_avg_str))
            else:
                anem_avg = False
            
            icmObj = {}
            icmObj['datetime'] = list(rh_data[:,0])
            icmObj['glb'] = list(glb_avg)
            icmObj['dbt'] = list(dbt_avg)
            icmObj['rh'] = list(rh_avg)
            if anem_avg:
                icmObj['anem'] = list(anem_avg)
            if test_objects[i].metabolic_rate != None:
                icmObj['met'] = test_objects[i].metabolic_rate
            else:
                icmObj['met'] = post_data['met']
            if test_objects[i].clothing_level != None:
                icmObj['clo'] = test_objects[i].clothing_level
            else:
                icmObj['clo'] = post_data['clo']
                    
            return icmObj
        else:
            return False 
    else:
        return icmData

def lightingIaqAcousticsCharts(test_objects, post_data, time_zone, deviceUuids, ieq_cat):
    chartType = post_data['chart_type']
    #ct is merely to separate spline from the others because of different x-axis parameters
    if chartType == 'column':
        ct = 'column'
        chartData = buildColumnChart(test_objects, post_data, time_zone, deviceUuids, ieq_cat)
        d = simplejson.dumps({'data':chartData[0], 'categories':chartData[1], 'alert': False, 'type': ct})
    elif chartType == 'boxplotST':
        ct = 'boxplot'
        chartData = buildBoxplotChartST(test_objects, post_data, time_zone, deviceUuids, ieq_cat)
        d = simplejson.dumps({'data':chartData[0], 'categories':chartData[1], 'alert': False, 'type': ct})
    elif chartType == 'spline':
        ct = 'spline'
        chartData = buildSplineChart(test_objects, post_data, time_zone, deviceUuids, ieq_cat)
        d = simplejson.dumps({'data':chartData, 'alert': False, 'type': ct})
    elif chartType == 'boxplotLT':
        ct = 'boxplot'
        chartData = buildBoxplotChartLT(test_objects, post_data, time_zone, deviceUuids, ieq_cat)
        d = simplejson.dumps({'data':chartData[0], 'categories':chartData[1], 'alert': False, 'type': ct})
    elif chartType == 'splineAvgDay':
        ct = 'splineAvgDay'
        chartData = buildAvgDaySplineChart(test_objects, post_data, time_zone, deviceUuids, ieq_cat)
        d = simplejson.dumps({'data':chartData[0], 'categories':chartData[1], 'alert': chartData[2], 'type': ct})
    elif chartType == 'boxplotAvgDay':
        ct = 'boxplot'
        chartData = buildAvgDayBoxplot(test_objects, post_data, time_zone, deviceUuids, ieq_cat)
        d = simplejson.dumps({'data':chartData[0], 'categories':chartData[1], 'alert': False, 'type': ct})  
    return d

def mapChart(test_objects, post_data, time_zone, deviceUuids):
    project_obj = Project.objects.get(pk=post_data['projectId'])
    info = []
    zone_list = {}
    obj_values = {}
    obj_colors = {}
    if 'conditions' in post_data:
        conditions = post_data['conditions']
    else:
        conditions = False
    map_obj = Map.objects.get(floor=Floor.objects.get(pk=post_data['floor']), project=Project.objects.get(pk=post_data['projectId']))
    if not post_data['ad']:
        wkd = post_data['wkd']
        sh = post_data['sh']
        eh = post_data['eh']
    #figure out what IEQ category we are dealing with
    if post_data['chart_type'] == 'map_ppd' or post_data['chart_type'] == 'map_top' or post_data['chart_type'] == 'plenum_map':
        ieq_cat = 'Thermal comfort'
    elif post_data['chart_type'] == 'map_lighting':
        ieq_cat = 'Lighting'
    elif post_data['chart_type'] == 'map_iaq':
        ieq_cat = 'IAQ'
    else:
        ieq_cat = 'Acoustics'
    for test_object in test_objects:
        #filter zones. there shouldn't really ever be more than one zone that matches per test object, but it will work if there is
        zones = test_object.zone.filter(zone_category__pmp_category__in=[ieq_cat])
        for zone in zones:
            if zone.zone_map:
                smap_uuids = deviceUuids[str(test_object.device)] #there will only be one uuid for lighting, acoustics, iaq, and plenum analysis
                #comfort maps
                if post_data['chart_type'] == 'map_top':
                    top_data = resampleSmapDataTop(smap_uuids['glb'], smap_uuids['dbt'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
                    no_rh = False
                    try:  
                        rh_data = resampleSmapData(smap_uuids['rh'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
                        rh_data = np.array(rh_data[0]['Readings'])
                    except:
                        no_rh = True
                    if len(top_data) > 0:                                    
                        #filter data
                        if not post_data['ad']:
                            top_data = filterData(top_data, time_zone, wkd, sh, eh)
                            if no_rh == False:
                                rh_data = filterData(rh_data, time_zone, wkd, sh, eh)
                        
                        color_type = 'tc_top'
                        label = 'Operative temperature (F)'
                        avg_val = round(np.mean(top_data[:,1]),1)
                        obj_values[test_object] = avg_val
                        obj_colors[test_object] = determineColor(avg_val,color_type, project_obj, conditions)
                        
                        if zone.name in zone_list:
                            prev_val = zone_list[zone.name][1]
                            new_val = round((avg_val + prev_val)/2,1)
                            zone_list[zone.name] = [zone, new_val, determineColor(new_val,color_type, project_obj)]
                        else:
                            zone_list[zone.name] = [zone, avg_val, determineColor(avg_val,color_type, project_obj)]
                else: #lighting, acoustics, iaq maps, and plenum motes
                    #currently lighting/iaq map charts are for the long-term tests, while the acoustics is for short term
                    #we may want to add flexibility on this in the future--but given the current toolkit setup, this makes the most sense
                    color_type = post_data['chart_type']
                    smapData = resampleSmapData(smap_uuids, int(post_data['resample_s']), post_data['start_time'], post_data['end_time'])
                    if color_type == 'map_lighting':
                        label = 'Light level (fc)'
                    elif color_type == 'map_iaq':
                        label = 'CO2 level (ppm)'
                    elif color_type == 'plenum_map':
                        label = 'Underfloor plenum temperature (F)'
                    else:
                        label = 'Sound level (dB(A))'
                        start = int(calendar.timegm(test_object.start_time.utctimetuple())) * 1000
                        end = int(calendar.timegm(test_object.end_time.utctimetuple())) * 1000
                        smapData = meanSmapData(smap_uuids, start, end) #this will return only one value
                    smapData = np.array(smapData[0]['Readings'])
                    if not post_data['ad']:
                        smapData = filterData(smapData, time_zone, wkd, sh, eh)
                    if len(smapData) > 0:
                        avg_val = round(np.mean(smapData[:,1]),1) #average the resampled data. the only reason we resample is to be able to filter first
                        obj_values[test_object] = avg_val
                        obj_colors[test_object] = determineColor(avg_val,color_type, project_obj, conditions)
                        if zone.name in zone_list:
                            prev_val = zone_list[zone.name][1]
                            new_val = round((avg_val + prev_val)/2,1)
                            zone_list[zone.name] = [zone, new_val, determineColor(new_val,color_type, project_obj, conditions)]
                        else:
                            zone_list[zone.name] = [zone, avg_val, determineColor(avg_val,color_type, project_obj, conditions)]
                
    for zone in zone_list:
        zone_obj = Zone.objects.get(project=post_data['projectId'], name=zone_list[zone][0])
        info.append((zone_obj.zone_map, {
            'html': 'Zone: ' + zone_obj.name + '<br>' + label + ': ' + str(zone_list[zone][1]),
            'style': {
                'fill_color': zone_list[zone][2],
                'stroke_color': zone_list[zone][2],
            },
        }))
    for test_object in test_objects: #this has to come after the zones because otherwise you can't click on them--despite stupidity of repeated loop here...
        if test_object.location:
            try:
                info.append((test_object.location, {
                    'html': repr(test_object.device).replace('<', '').replace('>', '') + '<br>' + label + ': ' + str(obj_values[test_object]),
                    'style': {
                        'fill_color': obj_colors[test_object],
                        'stroke_color': obj_colors[test_object],
                    },
                }))
            except:
                continue
     
    map = InfoMap(info, {
            'layers': ['image_layer'],
            'map_div_style': {'width':'100%', 'height':'450px'},
             'mapOptions':({
                   'max_extent': [map_obj.left_extent,map_obj.bottom_extent,map_obj.right_extent,map_obj.top_extent],
                   'max_resolution': map_obj.resolution,
                   'num_zoom_levels': map_obj.zoom_levels,
            })})
    return map

def ieqMapChart(test_objects, post_data, time_zone, deviceUuids, type):
    map_obj = Map.objects.get(floor=Floor.objects.get(pk=post_data['floor']), project=Project.objects.get(pk=post_data['projectId']))
    test_objects_s = test_objects #keep copy saved
    ieq_model = int(post_data['ieq_model']['name'])
    ieq_model_obj = IeqModel.objects.get(pk=ieq_model)
    #determine zones--need to loop through zones rather than test objects--lazy/dumb way of doing this, I know
    zone_list = []
    info = []
    st_fset = Set(IeqSpace.objects.filter(model=ieq_model_obj).values_list('space_type', flat=True).distinct())
    if type == 'tc':
        ieq_cat = 'Thermal comfort'
    elif type == 'lighting':
        ieq_cat = 'Lighting'
    elif type == 'iaq':
        ieq_cat = 'IAQ'
    else:
        ieq_cat = 'Acoustics'
    
    for test_object in test_objects_s:
        #filter zones. there shouldn't really ever be more than one zone that matches per test object, but it will work if there is
        zones = test_object.zone.filter(zone_category__pmp_category__in=[ieq_cat])
        for zone in zones:
            if zone.name in zone_list:
                continue
            else:
                zone_list.append(zone.name)

    for zone_i in zone_list:
        zone_obj = Zone.objects.get(name=zone_i)
        test_objects = test_objects_s.filter(zone=zone_obj)
        #return test_objects_s
        if len(test_objects) > 0:
            st = zone_obj.space_type
            if st not in st_fset:
                st = 'Default'
            f_data = buildIeqChart(st, test_objects, post_data, type, deviceUuids, time_zone)
            qc_data_z = f_data[3] #qc_data_all for this zone
            if f_data[1] != 'no data':
                if ieq_model == 4:
                    score = round(qc_data_z[0],0)
                else:
                    score = round(qc_data_z[0] + (.7*qc_data_z[1]) + (.35 * qc_data_z[2]),0)
                color = determineColor(score, 'score')
                #add the zone and color it
                info.append((zone_obj.zone_map, {
                    'html': 'Zone: ' + zone_obj.name + '<br>Score: ' + str(score),
                    'style': {
                        'fill_color': color,
                        'stroke_color': color,
                    },
                }))
            for test_object in test_objects: #add device location dot
                if test_object.location:
                    try:
                        score = f_data[5][test_object]
                        color = determineColor(score, 'score')
                    except:
                        color = '#FF00FF'
                    try:
                        info.append((test_object.location, {
                            'html': repr(test_object.device).replace('<', '').replace('>', '')+ '<br>Score: ' + str(f_data[5][test_object]),
                            'style': {
                                'fill_color': color,
                                'stroke_color': color,
                            },
                        }))
                    except:
                        continue
                    
    map = InfoMap(info, {
        'layers': ['image_layer'],
        'map_div_style': {'width':'100%', 'height':'450px'},
         'mapOptions':({
               'max_extent': [map_obj.left_extent,map_obj.bottom_extent,map_obj.right_extent,map_obj.top_extent],
               'max_resolution': map_obj.resolution,
               'num_zoom_levels': map_obj.zoom_levels,
        })})
    return map

def buildBoxplotChartST(test_objects, post_data, time_zone, deviceUuids, ieq_cat):
    categories = []
    seriesData = []
    seriesPoints = []
    outliers = []
    if not post_data['ad']:
        sh = post_data['sh']
        eh = post_data['eh']
        wkd = post_data['wkd']
    for i in xrange(len(test_objects)):
        uuid = deviceUuids[str(test_objects[i].device)]
        start = int(calendar.timegm(test_objects[i].start_time.utctimetuple())) * 1000
        end = int(calendar.timegm(test_objects[i].end_time.utctimetuple())) * 1000
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=[ieq_cat])
        zone = zones[0]
        categories.append(test_objects[i].end_time.astimezone(time_zone).strftime(dt_format) + ' ' + zone.name)
        try:
            smapData = resampleSmapData(uuid, 60, start, end)
            smapData = np.array(smapData[0]['Readings'])
            bp_data = fivenum(list(smapData[:,1]))
            seriesPoints.append(bp_data[0:5])
            if len(bp_data[5]) > 0:
                for elem in bp_data[5]:
                    outliers.append([i,elem])
        except:
            continue
    seriesData.append({'name': test_objects[i].device.name,'type': 'boxplot','data': seriesPoints})
    seriesData.append({'name': 'Outliers', 'type': 'scatter','marker': {'fillColor': 'white','lineColor': 'gray','lineWidth': 1, 'radius':3},'data': outliers})
    return [seriesData, categories]

def buildBoxplotChartLT(test_objects, post_data, time_zone, deviceUuids, ieq_cat):
    categories = []
    seriesData = []
    alert = False    
    outliers = []
    if not post_data['ad']:
        sh = post_data['sh']
        eh = post_data['eh']
        wkd = post_data['wkd']
    for i in xrange(len(test_objects)):
        seriesPoints = []
        st = int(post_data['start_time'])
        et = int(post_data['end_time'])
        increment = int(post_data['resample_s']) * 1000
        count = 0
        uuid = deviceUuids[str(test_objects[i].device)]
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=[ieq_cat])
        zone = zones[0]
        while et >= st and count <= 24:
            try:
                if ieq_cat == 'Thermal comfort': #this is a stupid way to handle the nsa hobo data which is 15 minute data which means it is wasteful to subsample it to 300s like the other data
                    smapData = resampleSmapDataFiltered(uuid, 900, st, st+increment)
                else:
                    smapData = resampleSmapData(uuid, 300, st, st+increment)
                smapData = np.array(smapData[0]['Readings'])
                if not post_data['ad']:
                    smapData = filterData(smapData, time_zone, wkd, sh, eh)
                if len(smapData) > 0:
                    bp_data = fivenum(list(smapData[:,1]))
                    if len(seriesData) == 0:
                        categories.append(datetime.datetime.utcfromtimestamp(smapData[0][0]/1000).replace(tzinfo=time_zone_utc).astimezone(time_zone).strftime('%H:%M'))
                    seriesPoints.append(bp_data[0:5])
                    if len(bp_data[5]) > 0:
                        for elem in bp_data[5]:
                            outliers.append([count,elem])
                    count += 1
                    if count > 24:
                        alert = True
                st += increment   
            except:
                count += 1
                if count > 24:
                    alert = True
                st += increment
                continue
        seriesData.append({'name': test_objects[i].device.name + ' ' + zone.name,'type': 'boxplot','data': seriesPoints})
    seriesData.append({'name': 'Outliers', 'type': 'scatter','marker': {'fillColor': 'white','lineColor': 'gray','lineWidth': 1, 'radius':3},'data': outliers})
    return [seriesData, categories, alert]

def buildAvgDayBoxplot(test_objects, post_data, time_zone, deviceUuids, ieq_cat):
    categories = []
    seriesData = []
    outliers = []
    wkd = post_data['wkd']
    if not post_data['ad']:
        sh = post_data['sh']
        eh = post_data['eh']
    for i in xrange(len(test_objects)):
        seriesPoints = []
        uuid = deviceUuids[str(test_objects[i].device)]
        smapData = resampleSmapData(uuid, int(post_data['resample_s']), post_data['start_time'], post_data['end_time'])
        smapData = np.array(smapData[0]['Readings'])
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=[ieq_cat])
        zone = zones[0]
        #run boxplot average day
        bp_data = avgDayBoxplot(smapData, time_zone, wkd)
        seriesPoints = []
        outliers = []
        for y in xrange(len(bp_data)):
            if not post_data['ad']:
                if y >= sh and y <= eh:
                    try:
                        if i == 0:
                            categories.append(str(y)+':00')
                        seriesPoints.append(bp_data[y][0:5])
                        if len(bp_data[y][5]) > 0:
                            for elem in bp_data[y][5]:
                                outliers.append([y-sh,elem])
                    except:
                        continue
            else:
                try:
                    if i == 0:
                        categories.append(str(y)+':00')
                    seriesPoints.append(bp_data[y][0:5])
                    if len(bp_data[y][5]) > 0:
                            for elem in bp_data[y][5]:
                                outliers.append([y,elem])
                except:
                    continue
        seriesData.append({'name': test_objects[i].device.name + ' ' + zone.name,'type':'boxplot','data': seriesPoints})
    seriesData.append({'name': 'Outliers', 'type': 'scatter','marker': {'fillColor': 'white','lineColor': 'gray','lineWidth': 1, 'radius':3},'data': outliers})  
    return [seriesData, categories]

def buildAvgDaySplineChart(test_objects, post_data, time_zone, deviceUuids, ieq_cat):
    categories = []
    seriesData = []
    alert = False
    wkd = post_data['wkd']
    if not post_data['ad']:
        sh = post_data['sh']
        eh = post_data['eh']
    for i in xrange(len(test_objects)):
        uuid = deviceUuids[str(test_objects[i].device)]
        smapData = resampleSmapData(uuid, int(post_data['resample_s']), post_data['start_time'], post_data['end_time'])
        smapData = np.array(smapData[0]['Readings'])
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=[ieq_cat])
        zone = zones[0]
        #run average day
        if len(smapData[:,1]) > 0:
            avgDayData = avgDay(smapData, time_zone, wkd)
            seriesPoints = []
            #return [smapData,'test',False]
            try:
                if len(avgDayData) > 0:
                    if not post_data['ad']:  # filter after the average day function because it requires continuous data
                        for y in xrange(len(avgDayData)):
                            if y >= sh and y <= eh:
                                seriesPoints.append(avgDayData[y])
                    else:
                        seriesPoints = avgDayData
                    seriesData.append({'name': test_objects[i].device.name + ' ' + zone.name,'type': 'spline','data': seriesPoints})            
                    if len(categories) < 1: #only need to create categories once
                        categories.extend(datetime.datetime.utcfromtimestamp(elem/1000).replace(tzinfo=time_zone_utc).astimezone(time_zone).strftime('%H:%M') for elem in list(smapData[:,0][:24]))
            except:
                pass
    return [seriesData, categories, alert]


def buildSplineChart(test_objects, post_data, time_zone, deviceUuids, ieq_cat):
    seriesData = []
    if not post_data['ad']:
        sh = post_data['sh']
        eh = post_data['eh']
        wkd = post_data['wkd']
    for i in xrange(len(test_objects)):
        uuid = deviceUuids[str(test_objects[i].device)]
        smapData = resampleSmapData(uuid, int(post_data['resample_s']), post_data['start_time'], post_data['end_time'])
        smapData = np.array(smapData[0]['Readings'])
        
        #figure out the right zone to give it the right label
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=[ieq_cat])
        zone = zones[0]
        try:
            if len(smapData[:,1]) > 0:
                if not post_data['ad']:  # filter after the average day function because it requires continuous data
                    smapData = filterData(smapData, time_zone, wkd, sh, eh, True)
                smapData = adjustTime(smapData, time_zone) #adjust to correct time_zone for highcharts
                dataPoints = []
                for elem in list(smapData):
                    try:
                        dataPoints.append([elem[0],round(elem[1], 1)])
                    except:
                        dataPoints.append([elem[0],None])
                seriesData.append({'name': test_objects[i].device.name + ' ' + zone.name,'type': 'spline','data': dataPoints})
        except:
            pass
    return seriesData

def buildColumnChart(test_objects, post_data, time_zone, deviceUuids, ieq_cat): #lighting, iaq, and acoustics charts
    seriesData = []
    categories = []
    data_points = []
    smapData = []
    if not post_data['ad']:
        sh = post_data['sh']
        eh = post_data['eh']
        wkd = post_data['wkd']
    for i in xrange(len(test_objects)):
        uuid = deviceUuids[str(test_objects[i].device)]    
        start = int(calendar.timegm(test_objects[i].start_time.utctimetuple())) * 1000
        end = int(calendar.timegm(test_objects[i].end_time.utctimetuple())) * 1000
        zones = test_objects[i].zone.filter(zone_category__pmp_category__in=[ieq_cat])
        zone = zones[0]
        categories.append(test_objects[i].end_time.astimezone(time_zone).strftime(dt_format) + ': ' + zone.name)
        if post_data['analysisType'] == 'acousticsST': #if it is an acoustics check, check for a manual entry of las_95 and report that instead of the the average value
            try:
                acoustics_obj = AcousticsTest.objects.get(test=test_objects[i])
                if acoustics_obj.las_95:
                    data_points.append(float(acoustics_obj.las_95))
                else:
                    smapData = meanSmapData(uuid, start, end)
            except:
                smapData = meanSmapData(uuid, start, end)
        else:
            smapData = meanSmapData(uuid, start, end)
        if len(smapData) > 0:
            data_points.append(round(smapData[0]['Readings'][0][1],1)) #add the one reading (which is the average of the light/sound/co2 level over the test period)
    if post_data['analysisType'] == 'iaqST':
        seriesData = [
            {'name': 'CO2 level',
            'type': 'column',
            'data':data_points}]
    elif post_data['analysisType'] == 'lightingST':
        seriesData = [
            {'name': 'Room illuminance',
            'type': 'column',
            'data':data_points}]
    elif post_data['analysisType'] == 'acousticsST':
        seriesData = [
            {'name': 'Sound level',
            'type': 'column',
            'data':data_points}]
    return [seriesData, categories]

def ieqModel(test_objects, post_data, start_date, end_date, type, deviceUuids, time_zone):
    ieq_model = int(post_data['ieq_model']['name'])
    ieq_model_obj = IeqModel.objects.get(pk=ieq_model)
    test_objects_s = test_objects #keep static copy of test_objects
    if type == 'tc':
        ieq_cat = "Thermal Comfort"
    elif type == 'iaq':
        ieq_cat = "IAQ"
    elif type == 'acc':
        ieq_cat = "Acoustics"
    else:
        ieq_cat = "Lighting"

    st_List = []
    for test_object in test_objects_s:
        #filter zones by ieq_category. there shouldn't really ever be more than one zone that matches per test object, but it will work if there is
        zones = test_object.zone.filter(zone_category__pmp_category__in=[ieq_cat])
        for zone in zones:
            if zone.space_type in st_List:
                continue
            else:
                st_List.append(zone.space_type)
                                
    #create the set of space type model definitions
    st_set = Set(st_List)
    #return st_set
    st_fset = Set(IeqSpace.objects.filter(model=ieq_model_obj).exclude(space_type='Default').values_list('space_type', flat=True).distinct())
    st_final = st_set.intersection(st_fset) #find the list of space types that have a model definition and have tests associated with that space type
    seriesData = []
    st_names = []
    qc_data = []
    bp_data = []
    legend = {}
    total_data = {}
    qc1_l,qc2_l,qc3_l,qc4_l,qc5_l = [],[],[],[],[]
    alert = False
    selectedOption = 0
   
    if len(st_final) > 0: #run through the st model definition first
        for st in st_final:
            test_objects = test_objects_s.filter(zone__space_type=st)
            if len(test_objects) > 0:
                test_objects
                st_names.append(st)        
                f_data = buildIeqChart(st, test_objects, post_data, type, deviceUuids, time_zone)
                qc_data_st = f_data[0]
                alert = f_data[1]
                qc_data_points = f_data[2]
                qc_data_all = f_data[3]
                legend[st] = f_data[6]
                total_data[st] = f_data[4]
                qc1_l.append(qc_data_all[0])
                qc2_l.append(qc_data_all[1])
                qc3_l.append(qc_data_all[2])
                qc4_l.append(qc_data_all[3])
                qc5_l.append(qc_data_all[4])
                qc_data = [qc1_l, qc2_l, qc3_l, qc4_l, qc5_l]
                for i in xrange(len(qc_data_points)):
                    bp_data.append({'category':st, 'class':str(i),'data': fivenum(qc_data_points[i])})
               
                if post_data['save_model']:
                    #if post_data['ieq_save']['name'] != "":
                    #    modelInstance = IeqData.objects.get(pk=post_data['ieq_save']['name'])
                    #else:
                    st_obj = IeqSpace.objects.get(model=ieq_model_obj, space_type=st)
                    proj_obj = Project.objects.get(pk=post_data['projectId'])
                    try:
                        modelInstance = IeqData.objects.get(project=proj_obj, ieq_space=st_obj, ieq_category=ieq_cat)
                    except:
                        modelInstance = IeqData(project=proj_obj, ieq_space=st_obj, ieq_category=ieq_cat)
                    #there must be some way to make this loop more intelligent...
                    for i in xrange(len(qc_data_st)):
                        if i == 0:
                            modelInstance.quality_cat1 = str(qc_data_st[i]) #have to convert float to string in order to save as decimal in db...weird
                        if i == 1:
                            modelInstance.quality_cat2 = str(qc_data_st[i])
                        if i == 2:
                            modelInstance.quality_cat3 = str(qc_data_st[i])
                        if i == 3:
                            modelInstance.quality_cat4 = str(qc_data_st[i])
                        if i == 4:
                            modelInstance.quality_cat5 = str(qc_data_st[i])
                    modelInstance.save()
                    #if post_data['ieq_save']['name'] == "":
                    #    newInstance = IeqData.objects.latest('id')
                    #    selectedOption = newInstance.pk
                    #else:
                    #    selectedOption = 0

    if len(st_final) < len(st_List): #now take care of all the 'default' space type model tests
        query = []
        for st in st_final: #exclude the test objects that have already been taken care of above
            query.append('.exclude(zone__space_type=\'%s\')' % st)
        query = ''.join(query)
        test_objects = eval('test_objects_s' + query)
        if len(test_objects) > 0:
            st_names.append('Default and other space types')
            f_data = buildIeqChart('Default', test_objects, post_data, type, deviceUuids, time_zone)
            alert = f_data[1]
            if alert != 'no data':
                qc_data_st = f_data[0]
                qc_data_points = f_data[2]
                qc_data_all = f_data[3]
                legend['Default'] = f_data[6]
                total_data['Default'] = f_data[4]
                qc1_l.append(qc_data_all[0])
                qc2_l.append(qc_data_all[1])
                qc3_l.append(qc_data_all[2])
                qc4_l.append(qc_data_all[3])
                qc5_l.append(qc_data_all[4])
                qc_data = [qc1_l, qc2_l, qc3_l, qc4_l, qc5_l]
                for i in xrange(len(qc_data_points)):
                    bp_data.append({'category':'Default and other space types', 'class':str(i),'data': fivenum(qc_data_points[i])})
                
                if post_data['save_model']:
                    #if post_data['ieq_save']['name'] != "":
                    #    modelInstance = IeqData.objects.get(pk=post_data['ieq_save']['name'])
                    #else:
                    st_obj = IeqSpace.objects.get(model=ieq_model_obj, space_type='Default')
                    proj_obj = Project.objects.get(pk=post_data['projectId'])
                    try:
                        modelInstance = IeqData.objects.get(project=proj_obj, ieq_space=st_obj, ieq_category=ieq_cat)
                    except:
                        modelInstance = IeqData(project=proj_obj, ieq_space=st_obj, ieq_category=ieq_cat)
                    #there must be some way to make this loop more intelligent...
                    for i in xrange(len(qc_data_st)):
                        if i == 0:
                            modelInstance.quality_cat1 = str(qc_data_st[i]) #have to convert float to string in order to save as decimal in db...weird
                        if i == 1:
                            modelInstance.quality_cat2 = str(qc_data_st[i])
                        if i == 2:
                            modelInstance.quality_cat3 = str(qc_data_st[i])
                        if i == 3:
                            modelInstance.quality_cat4 = str(qc_data_st[i])
                        if i == 4:
                            modelInstance.quality_cat5 = str(qc_data_st[i])
                    modelInstance.save()
                    #if post_data['ieq_save']['name'] == "":
                    #    newInstance = IeqData.objects.latest('id')
                    #    selectedOption = newInstance.pk
                    #else:
                    #    selectedOption = 0
                        
    #generate the highcharts series
    ieq_space_obj = IeqSpace.objects.get(model=ieq_model_obj, space_type='Default')
    if isinstance(ieq_space_obj.qc5_tc_summer_condition_low, types.NoneType) == False:
        colors = [['green', 'yellow', 'orange', 'red', 'purple'],['#43bd79', '#c5c745', '#c78e45', '#c75745', 'purple']]
    elif isinstance(ieq_space_obj.qc4_tc_summer_condition_low, types.NoneType) == False:
        colors = [['green', 'yellow', 'orange', 'red'],['#43bd79', '#c5c745', '#c78e45', '#c75745']]
    elif isinstance(ieq_space_obj.qc3_tc_summer_condition_low, types.NoneType) == False:
        colors = [['green', 'yellow', 'red'],['#43bd79', '#c5c745', '#c75745']]
    else:
        colors = [['green', 'red'],['#43bd79', '#c75745']]
    try:
        if len(qc_data[0]) > 0:
            for i in reversed(xrange(len(qc_data))):
                if sum(qc_data[i]) > 0:
                    name = 'Assessment Class ' + str(i+1)
                    seriesData.append({
                    'name': name, 'data': qc_data[i], 'color': colors[0][i]
                           })
    except:
        alert = 'no data'
        
    #generate the sidebar legend
    legend_html = ''
    for st in legend:
        ieq_space = IeqSpace.objects.get(model=ieq_model_obj, space_type=st)
        units = getattr(ieq_space,'units_%s' % type, None)
        if st == 'Default':
            legend_html += '<br><b>Default and other space types</b><br>'
        else:
            legend_html += '<br><b>' + st + '</b><br>'
        for i in xrange(0,len(legend[st]),2):
            if i == 0:
                j=1
            else:
                j+=1
            if legend[st][i] != None and j <= len(seriesData):
                legend_html += "<div class=\"series_color\" style=\"background-color: " + colors[0][j-1] + "\"></div>&nbsp; AC" + str(j) + ": " + str(round(legend[st][i],1)) + ' &le; ' + units + ' &lt; ' + str(round(legend[st][i+1],1)) + '<div style=\"clear: left; margin: 12px;\"></div>'
        legend_html+='Total readings analyzed: ' + str(total_data[st]) + '<br>'
    
    #generate boxplot series
    bp_series_data = {}
    bp_categories = {}
    for category in st_names:
        bp_series_data[category] = []
        bp_categories[category] = []
        seriesPoints = []
        outliers = []
        bp_cat_count = 0
        for boxplot in bp_data:
            if boxplot['category'] == category:
                bp_categories[category].append('Class ' + str(bp_cat_count + 1))
                seriesPoints.append(boxplot['data'][0:5])
                if len(boxplot['data'][5]) > 0:
                    for elem in boxplot['data'][5]:
                        outliers.append([bp_cat_count,elem])
                bp_cat_count += 1
        bp_series_data[category].append({'name': category,'type': 'boxplot','data': seriesPoints})
        bp_series_data[category].append({'name': 'Outliers', 'type': 'scatter','marker': {'fillColor': 'white','lineColor': 'gray','lineWidth': 1, 'radius':3},'data': outliers})
    
    return {'seriesData':seriesData, 'categories': st_names, 'alert': alert, 'bp_data': bp_series_data, 'bp_categories': bp_categories, 'legend': legend_html}

def buildIeqChart(st, test_objects, post_data, type, deviceUuids, time_zone): #sub function for actually making the chart                    
    #build the ieq chart by building qc_data
    ieq_model = int(post_data['ieq_model']['name'])
    ieq_model_obj = IeqModel.objects.get(pk=ieq_model)
    total_data = 0
    obj_scores = {}
    alert = 'false'
    qc_data_l = []
    qc_data_st = []
    qc1 = qc2 = qc3 = qc4 = qc5 = 0
    qc1_data,qc2_data,qc3_data,qc4_data,qc5_data = [],[],[],[],[]
    ieq_space = IeqSpace.objects.get(model=ieq_model_obj, space_type=st)
    #determine winter/summer months for tc analysis
    if ieq_space.summer_start_month and ieq_space.summer_end_month:
        s_month = int(ieq_space.summer_start_month)
        e_month = int(ieq_space.summer_end_month)
    else:
        s_month = 0
        e_month = 13
    #create list of conditions to pass back for legend creation
    start_month = datetime.datetime.fromtimestamp(post_data['start_time']/1000, time_zone).astimezone(time_zone).month
    if type == 'tc':
        if start_month >= s_month and start_month <= e_month:
            season = 'summer'
        else:
            season = 'winter'
        legend = [getattr(ieq_space,'qc1_tc_%s_condition_low' % season, None),getattr(ieq_space,'qc1_tc_%s_condition_high' % season, None),getattr(ieq_space,'qc2_tc_%s_condition_low' % season, None),getattr(ieq_space,'qc2_tc_%s_condition_high' % season, None),getattr(ieq_space,'qc3_tc_%s_condition_low' % season, None),getattr(ieq_space,'qc3_tc_%s_condition_high' % season, None),getattr(ieq_space,'qc4_tc_%s_condition_low' % season, None),getattr(ieq_space,'qc4_tc_%s_condition_high' % season, None),getattr(ieq_space,'qc5_tc_%s_condition_low' % season, None),getattr(ieq_space,'qc5_tc_%s_condition_high' % season, None)]
    else:
        legend = [getattr(ieq_space,'qc1_%s_condition_low' % type, None),getattr(ieq_space,'qc1_%s_condition_high' % type, None),getattr(ieq_space,'qc2_%s_condition_low' % type, None),getattr(ieq_space,'qc2_%s_condition_high' % type, None),getattr(ieq_space,'qc3_%s_condition_low' % type, None),getattr(ieq_space,'qc3_%s_condition_high' % type, None),getattr(ieq_space,'qc4_%s_condition_low' % type, None),getattr(ieq_space,'qc4_%s_condition_high' % type, None),getattr(ieq_space,'qc5_%s_condition_low' % type, None),getattr(ieq_space,'qc5_%s_condition_high' % type, None)]
    for i in xrange(len(test_objects)):
        qc1_obj = qc2_obj = qc3_obj = qc4_obj = qc5_obj = 0
        smap_uuids = deviceUuids[str(test_objects[i].device)]
        if type == 'tc': #thermal comfort data generation
            no_rh = False
            if ieq_model == 2 or ieq_model == 4:
                try:  
                    rh_data = resampleSmapData(smap_uuids['rh'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
                    rh_data = np.array(rh_data[0]['Readings'])
                except:
                    no_rh = True
            data = resampleSmapDataTop(smap_uuids['glb'], smap_uuids['dbt'], post_data['resample_s'], post_data['start_time'], post_data['end_time'])
        elif type == 'acc':
            try:
                acoustics_obj = AcousticsTest.objects.get(test=test_objects[i])
                if acoustics_obj.las_95:
                    data = [[int(calendar.timegm(test_objects[i].end_time.utctimetuple())) * 1000, float(acoustics_obj.las_95)]]
            except:
                pass
        else: #lighting, iaq
            if post_data['time_length'] == 'st':
                start = int(calendar.timegm(test_objects[i].start_time.utctimetuple())) * 1000
                end = int(calendar.timegm(test_objects[i].end_time.utctimetuple())) * 1000
                data = meanSmapData(smap_uuids, start, end)
            else:
                data = resampleSmapData(smap_uuids, post_data['resample_s'], post_data['start_time'], post_data['end_time'])
            data = np.array(data[0]['Readings'])
        #filter data
        if not post_data['ad']:
            wkd = post_data['wkd']
            sh = post_data['sh']
            eh = post_data['eh']
            if type == 'tc':
                data = filterData(data, time_zone, wkd, sh, eh)
                if (ieq_model == 2 or ieq_model == 4) and no_rh == False:
                    rh_data = filterData(rh_data, time_zone, wkd, sh, eh)
            else:
                data = filterData(data, time_zone, wkd, sh, eh)
        
        #return data
        if ieq_model == 2 or ieq_model == 4: #convert to index of ncube model and PMP model
            points = []
            for j in xrange(len(data)):
                try:
                    if type == 'tc':
                        if no_rh == False:
                            pmv_ppd = pmv(FtoC(data[j][1]),FtoC(data[j][1]),0.15,rh_data[j][1],float(post_data['met']), float(post_data['clo']), 0)
                            if ieq_model == 4:
                                points.append([data[j][0], pmv_ppd[0]]) #pmv for pmp model
                            else:
                                points.append([data[j][0], (100-pmv_ppd[1])]) #pps for ncube
                    elif type == 'lighting' and ieq_model == 2:
                        ln_lux = log(log(data[j][1] * 10.76391))
                        l_index = (-176.16*pow(ln_lux,2)) + (738.4*ln_lux) - 690.29
                        points.append([data[j][0], l_index])
                    #elif type == 'acc' and ieq_model == 2:
                        
                    #    points.append([data[j][0],data[j][1]]) 
                    elif type == 'iaq' and ieq_model == 2: #iaq
                        iaq_index = 100 - (395 * exp(-15.15 * pow(data[j][1]-400,-.25)))
                        points.append([data[j][0],iaq_index])
                except:
                    pass
        
        #determine which set of data to use
        if ieq_model == 2 or (ieq_model == 4 and type == 'tc'):
            master_data = points
        else:
            master_data = data
        #return points
        for point in master_data:
            if type == 'tc': #stupid hard coded for tc because of summer/winter conditions--could probably be more intelligent about this
                p_month = datetime.datetime.fromtimestamp(point[0]/1000, time_zone).astimezone(time_zone).month
                #check for summer or winter condition (if specified)
                if p_month >= s_month and p_month <= e_month: #summer                                 
                    if point[1] >= float(ieq_space.qc1_tc_summer_condition_low) and point[1] <= float(ieq_space.qc1_tc_summer_condition_high):   #check category 1
                        qc1+=1
                        qc1_obj+=1
                        qc1_data.append(point[1]) #this is for generating a boxplot of the data
                    else:
                        if ieq_space.qc2_tc_summer_condition_low and ieq_space.qc2_tc_summer_condition_high:
                            if point[1] >= float(ieq_space.qc2_tc_summer_condition_low) and point[1] <= float(ieq_space.qc2_tc_summer_condition_high):   #check category 2
                                qc2+=1
                                qc2_obj+=1
                                qc2_data.append(point[1])
                            else:
                                if ieq_space.qc3_tc_summer_condition_low and ieq_space.qc3_tc_summer_condition_high:
                                    if point[1] >= float(ieq_space.qc3_tc_summer_condition_low) and point[1] <= float(ieq_space.qc3_tc_summer_condition_high):   #check category 3
                                        qc3+=1
                                        qc3_obj+=1
                                        qc3_data.append(point[1])
                                    else:
                                        if ieq_space.qc4_tc_summer_condition_low and ieq_space.qc4_tc_summer_condition_high:
                                            if point[1] >= float(ieq_space.qc4_tc_summer_condition_low) and point[1] <= float(ieq_space.qc4_tc_summer_condition_high):   #check category 4
                                                qc4+=1
                                                qc4_obj+=1
                                                qc4_data.append(point[1])
                                            else:
                                                if ieq_space.qc5_tc_summer_condition_low and ieq_space.qc5_tc_summer_condition_high:
                                                    if point[1] >= float(ieq_space.qc5_tc_summer_condition_low) and point[1] <= float(ieq_space.qc5_tc_summer_condition_high):   #check category 5
                                                        qc5+=1
                                                        qc5_obj+=1
                                                        qc5_data.append(point[1])
                else: #winter
                    if point[1] >= float(ieq_space.qc1_tc_winter_condition_low) and point[1] <= float(ieq_space.qc1_tc_winter_condition_high):   #check category 1
                        qc1+=1
                        qc1_obj+=1
                        qc1_data.append(point[1])
                    else:
                        if ieq_space.qc2_tc_winter_condition_low and ieq_space.qc2_tc_winter_condition_high:
                            if point[1] >= float(ieq_space.qc2_tc_winter_condition_low) and point[1] <= float(ieq_space.qc2_tc_winter_condition_high):   #check category 2
                                qc2+=1
                                qc2_obj+=1
                                qc2_data.append(point[1])
                            else:
                                if ieq_space.qc3_tc_winter_condition_low and ieq_space.qc3_tc_winter_condition_high:
                                    if point[1] >= float(ieq_space.qc3_tc_winter_condition_low) and point[1] <= float(ieq_space.qc3_tc_winter_condition_high):   #check category 3
                                        qc3+=1
                                        qc3_obj+=1
                                        qc3_data.append(point[1])
                                    else:
                                        if ieq_space.qc4_tc_winter_condition_low and ieq_space.qc4_tc_winter_condition_high:
                                            if point[1] >= float(ieq_space.qc4_tc_winter_condition_low) and point[1] <= float(ieq_space.qc4_tc_winter_condition_high):   #check category 4
                                                qc4+=1
                                                qc4_obj+=1
                                                qc4_data.append(point[1])
                                            else:
                                                if ieq_space.qc5_tc_winter_condition_low and ieq_space.qc5_tc_winter_condition_high:
                                                    if point[1] >= float(ieq_space.qc5_tc_winter_condition_low) and point[1] <= float(ieq_space.qc5_tc_winter_condition_high):   #check category 5
                                                        qc5+=1
                                                        qc5_obj+=1
                                                        qc5_data.append(point[1])
            else: #for lighting, acoustics, iaq
                if type == 'iaq' and ieq_model != 2:
                    point = [point[0], point[1] - 400] #get CO2 above background (this should eventually be configurable as another real-time value_                                 
                if type == 'acc' and ieq_model == 2:
                    if point[1] >= 40: #this is space-type stupid right now. just assuming fixed design noise level of 40 dbA.
                        point = [point[0],100-(2*(point[1]-40))]
                    else:
                        point = [point[0],100]
                if point[1] >= float(getattr(ieq_space,'qc1_%s_condition_low' % type, None)) and point[1] <= float(getattr(ieq_space,'qc1_%s_condition_high' % type, None)):   #check category 1
                    qc1+=1
                    qc1_obj+=1
                    qc1_data.append(point[1])
                else:
                    if getattr(ieq_space,'qc2_%s_condition_low' % type, None) >= 0 and getattr(ieq_space,'qc2_%s_condition_high' % type, None):
                        if point[1] >= float(getattr(ieq_space,'qc2_%s_condition_low' % type, None)) and point[1] <= float(getattr(ieq_space,'qc2_%s_condition_high' % type, None)):   #check category 2
                            qc2+=1
                            qc2_obj+=1
                            qc2_data.append(point[1])
                        else:
                            if getattr(ieq_space,'qc3_%s_condition_low' % type, None) >= 0 and getattr(ieq_space,'qc3_%s_condition_high' % type, None):
                                if point[1] >= float(getattr(ieq_space,'qc3_%s_condition_low' % type, None)) and point[1] <= float(getattr(ieq_space,'qc3_%s_condition_high' % type, None)):   #check category 3
                                    qc3+=1
                                    qc3_obj+=1
                                    qc3_data.append(point[1])
                                else:
                                    if getattr(ieq_space,'qc4_%s_condition_low' % type, None) >= 0 and getattr(ieq_space,'qc4_%s_condition_high' % type, None):
                                        if point[1] >= float(getattr(ieq_space,'qc4_%s_condition_low' % type, None)) and point[1] <= float(getattr(ieq_space,'qc4_%s_condition_high' % type, None)):   #check category 4
                                            qc4+=1
                                            qc4_obj+=1
                                            qc4_data.append(point[1])
                                        else:
                                            if getattr(ieq_space,'qc5_%s_condition_low' % type, None) >= 0 and getattr(ieq_space,'qc5_%s_condition_high' % type, None):
                                                if point[1] >= float(getattr(ieq_space,'qc5_%s_condition_low' % type, None)) and point[1] <= float(getattr(ieq_space,'qc5_%s_condition_high' % type, None)):   #check category 5
                                                    qc5+=1
                                                    qc5_obj+=1
                                                    qc5_data.append(point[1])
        try:    
            total_data+=len(data[:,1]) #keep track of the total number of data points for consistency check
        except:
            pass
        #determine the score for each individual test_object
        qc_sum = qc1_obj+qc2_obj+qc3_obj+qc4_obj+qc5_obj
        try:
            if ieq_model == 4:
                score = round(float(qc1_obj)/qc_sum * 100,0)
            else:
                score = round(float(qc1_obj)/qc_sum * 100 + .7*float(qc2_obj)/qc_sum*100 + .35 * float(qc3_obj)/qc_sum*100,0)
            obj_scores[test_objects[i]] = score
        except:
            continue
        #end test object for loop
    qc_sum = qc1+qc2+qc3+qc4+qc5
    if qc_sum > 0:
        if qc_sum != total_data: #note a problem if something falls out of all categories...
            alert = 'qc_sum != total_data'
        else:
            try:
                alert = master_data[len(data[:,1])-1:].tolist()
            except:
                alert = total_data
            
        #determine the percentages for each category and add it to the list for that category
        qc1_v = round(float(qc1)/qc_sum * 100,1)
        qc2_v = round(float(qc2)/qc_sum * 100,1)
        qc3_v = round(float(qc3)/qc_sum * 100,1)
        qc4_v = round(float(qc4)/qc_sum * 100,1)
        qc5_v = round(float(qc5)/qc_sum * 100,1)
        qc_data_all = [qc1_v, qc2_v, qc3_v, qc4_v, qc5_v] #for keeping a list in order to save in DB
        qc_data_st = [qc for qc in qc_data_all if qc > 0] #only save the categories that are above 0
        qc_data_points = [qc1_data, qc2_data, qc3_data, qc4_data, qc5_data] #for generating boxplot
        qc_data_points = [qc_data_point for qc_data_point in qc_data_points if len(qc_data_point) > 0] #only take the data arrays that have data
    
        return [qc_data_st, alert, qc_data_points, qc_data_all, total_data, obj_scores, legend]
    else:
        return [0,'no data',0,0,0,0,0]

def resampleSmapData(uuid, resample, start, end):
    #check times--for ICMs in a different time zone...
    #currently setup to take means of the resample rate (window starts at time start)
    c = smap.archiver.client.SmapClient(base='http://new.openbms.org/backend', key=['WE4iJWG7k575AluJ9RJyAZs25UO72Xu0b4RA','SA2nYWuHrJxmPNK96pdLKhnSSYQSPdALkvnA'])
    queryS = 'apply window(mean, field=\"second\", width=' + str(resample) + ') to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + uuid + '\"' 
    data = c.query(queryS)
    return data

def resampleSmapDataFiltered(uuid, resample, start, end):
    #same as above, but filters for reasonable temperature values--only used for plenum analysis right now (stupid solution to problem with HOBO data....)
    #currently setup to take means of the resample rate (window starts at time start)
    c = smap.archiver.client.SmapClient(base='http://new.openbms.org/backend', key=['WE4iJWG7k575AluJ9RJyAZs25UO72Xu0b4RA','SA2nYWuHrJxmPNK96pdLKhnSSYQSPdALkvnA'])
    queryS = 'apply <=110 < >=40 < window(mean, field=\"second\", width=' + str(resample) + ') to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + uuid + '\"' 
    data = c.query(queryS)
    return data

def resampleSmapDataTop(glb_uuid, dbt_uuid, resample, start, end):
    c = smap.archiver.client.SmapClient(base='http://new.openbms.org/backend', key=['WE4iJWG7k575AluJ9RJyAZs25UO72Xu0b4RA','SA2nYWuHrJxmPNK96pdLKhnSSYQSPdALkvnA'])
    #queryS = 'apply mean(axis=1) < paste < window(mean, field=\"second\", width=' + str(resample) + ') to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + glb_uuid + '\" or uuid = \"' + dbt_uuid + '\"' 
    queryGlb = 'apply window(mean, field=\"second\", width=' + str(resample) + ') to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + glb_uuid + '\"'
    queryDbt = 'apply window(mean, field=\"second\", width=' + str(resample) + ') to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + dbt_uuid + '\"'
    dataGlb = c.query(queryGlb)
    dataDbt = c.query(queryDbt)
    dataGlb = np.array(dataGlb[0]['Readings'])
    dataDbt = np.array(dataDbt[0]['Readings'])
    dataTop = []
    for i in xrange(len(dataGlb)):
        if dataGlb[i][0] == dataDbt[i][0]:
            top = (CtoF(globetemp(FtoC(dataDbt[i][1]), 0.15, FtoC(dataGlb[i][1]), 0.038, 0.95)) + dataDbt[i][1]) / 2
            dataTop.append([dataGlb[i][0], top])
    return np.array(dataTop)

def meanSmapData(uuid, start, end):
    c = smap.archiver.client.SmapClient(base='http://new.openbms.org/backend', key=['WE4iJWG7k575AluJ9RJyAZs25UO72Xu0b4RA','SA2nYWuHrJxmPNK96pdLKhnSSYQSPdALkvnA'])
    #figure out how to get cache working?
    queryS = 'apply mean to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + uuid + '\"' 
    data = c.query(queryS)
    return data

def avgDay(data, time_zone, wkd):
    day_data = [[] for i in range(24)]
    avg_day = []
    for i in xrange(len(data)):
        indexH = datetime.datetime.fromtimestamp(data[i][0]/1000, time_zone).hour
        dow = datetime.datetime.fromtimestamp(data[i][0]/1000, time_zone).weekday() 
        if wkd:
            if dow < 5:
                day_data[indexH].append(data[i][1])
        else:
            day_data[indexH].append(data[i][1])
    for i in xrange(len(day_data)):
        try:
            avg_day.append(sum(day_data[i])/len(day_data[i]))
        except:
            continue
    return avg_day

def avgDayComfort(data, time_zone, wkd):
    start_hour = datetime.datetime.fromtimestamp(data[0][0]/1000).hour
    end_hour = (datetime.datetime.fromtimestamp(data[0][0]/1000) + datetime.timedelta(hours=23)).hour
    day_data = np.array(0)
    day_count = 0
    avg_day = np.array(0)
    for i in xrange(0,len(data),24):
        try:
            if datetime.datetime.fromtimestamp(data[i][0]/1000).hour == start_hour and datetime.datetime.fromtimestamp(data[i+23][0]/1000).hour == end_hour: #make sure full day
                if wkd:
                    dow = datetime.datetime.fromtimestamp(data[i][0]/1000, time_zone).weekday()
                    if dow < 5:
                        day_data = day_data + data[i:i+24]
                        day_count += 1
                else:
                    day_data = day_data + data[i:i+24]
                    day_count += 1
        except:
            continue
    avg_day = day_data/day_count    
    for i in xrange(0,24): #dumb way of resetting times...
        avg_day[i][0] = data[i][0]
    return avg_day

def avgDayBoxplot(data, time_zone, wkd):
    day_data = [[] for i in range(24)]
    bp_data = [[] for i in range(24)]
    for i in xrange(len(data)):
        indexH = datetime.datetime.fromtimestamp(data[i][0]/1000, time_zone).hour
        dow = datetime.datetime.fromtimestamp(data[i][0]/1000, time_zone).weekday() 
        if wkd:
            if dow > 0 and dow < 5:
                day_data[indexH].append(data[i][1])
        else:
            day_data[indexH].append(data[i][1])
    for i in xrange(len(day_data)):
        try:
            hourBP = fivenum(day_data[i])
            bp_data[i] = hourBP
        except:
            continue
    return bp_data

def fivenum(v):
    try:
        np.sum(v)
    except TypeError:
        print('Error: you must provide a list or array of only numbers')
    q1 = scoreatpercentile(v,25)
    q3 = scoreatpercentile(v,75)
    iqd = q3-q1
    md = np.median(v)
    whisker = 1.5*iqd
    outliers = []
    for point in reversed(v):
        if point > q3 + whisker or point < q1 - whisker:
            outliers.append(round(point,1))
            v.remove(point)
    min = np.min(v)
    max = np.max(v)
    return [round(min,1), round(q1,1), round(md,1), round(q3,1), round(max,1), outliers]

def deviceSensors(deviceList, projectId):
    query = []
    if len(deviceList) > 0:
        for i in xrange(len(deviceList)):
            if i + 1 < len(deviceList):
                query.append('Q(device__name=\'%s\') | ' % deviceList[i])
            else:
                query.append('Q(device__name=\'%s\'))' % deviceList[i])
        query = ''.join(query)
        sensors = eval('Sensor.objects.filter(project=projectId).filter(' + query)
        return sensors
    else:
        return

def buildStrat(smap_uuids, start, end, sensorHeights, comfort):
    #connection to smap client
    c = smap.archiver.client.SmapClient(base='http://new.openbms.org/backend', key=['WE4iJWG7k575AluJ9RJyAZs25UO72Xu0b4RA','SA2nYWuHrJxmPNK96pdLKhnSSYQSPdALkvnA'])
    
    strat_data = []
    value_sum = 0
    cart_values = {}
    count = 0
    for uuid in smap_uuids:
        queryS = 'apply mean < <=110 < >=40 to data in (' + str(start) + ',' + str(end) + ') limit 10000000 where uuid = \"' + uuid + '\"' 
        #return queryS
        smapData = c.query(queryS)
        try:
            smapData = smapData[0]['Readings']    
            value = smapData[0][1]
            if comfort is False:
                strat_data.append([value,sensorHeights[uuid]])
            else: #for strat comfort chart--compute both arithmetic mean and weighted mean--determine which one to use at end
                #this is hard coded for heights of mobile cart (PUCC)
                value_sum += value #keep a running sum of the temps
                if 60 < sensorHeights[uuid] < 70: #hard coding this range is a bit awkward--means that our devices need to have a sensor at 67"...which is typical, but will break this if we don't
                    value_high = value #the temperature at head height
                    cart_values['67'] = value
                elif 0 < sensorHeights[uuid] < 5:
                    value_low = value #the temperature at ankle height
                    cart_values['4'] = value
                elif sensorHeights[uuid] == 10:
                    cart_values['10'] = value
                elif sensorHeights[uuid] == 24:
                    cart_values['24'] = value
                elif sensorHeights[uuid] == 48:
                    cart_values['48'] = value
                count += 1
        except:
            continue
    if comfort is False:
        strat_data = sorted(strat_data, key=itemgetter(1))
    else:
        try:
            temp_oz = ( (6*((cart_values['4']+cart_values['10'])/2)) + (14*((cart_values['24']+cart_values['10'])/2)) + (24*((cart_values['24']+cart_values['48'])/2)) + (19*((cart_values['48']+cart_values['67'])/2)) ) / 63
            strat_data.append([temp_oz, value_high - value_low])
        except:
            try:
                strat_data.append([value_sum/count, value_high - value_low]) #occupied zone average, stratification
            except:
                return strat_data
    return strat_data

def adjustTime(data, time_zone):
    adjusted_data = []
    for point in data:
        pyDate = datetime.datetime.fromtimestamp(point[0]/1000, time_zone)
        adjusted_data.append([int(calendar.timegm(pyDate.timetuple())*1000),point[1]])
    return np.array(adjusted_data)

def filterData(data, time_zone, wkd, sh, eh, create_nulls=False):
    filtered_data = []
    for point in data:
        pyDate = datetime.datetime.fromtimestamp(point[0]/1000, time_zone)
        dy = pyDate.weekday()
        hr = pyDate.hour
        if wkd:
            if hr >= sh and hr <= eh and dy != 5 and dy != 6:
                filtered_data.append([point[0],point[1]])
            elif create_nulls:
                filtered_data.append([point[0],None])
        else:
            if hr >= sh and hr <= eh:
                filtered_data.append([point[0],point[1]])
            elif create_nulls:
                filtered_data.append([point[0],None])
    return np.array(filtered_data)

def globetemp(ta, vel, tglobe, diameter, emissivity):
  return pow(pow(tglobe + 273, 4) + (1.1 * pow(10,8) * pow(vel, 0.6)) / (emissivity * pow(diameter,0.4)) * (tglobe - ta), 0.25) - 273;

def pmv(ta, tr, vel, rh, met, clo, wme):
    # returns [pmv, ppd]
    # ta, air temperature (C)
    # tr, mean radiant temperature (C)
    # vel, relative air velocity (m/s)
    # rh, relative humidity (%) Used only this way to input humidity level
    # met, metabolic rate (met)
    # clo, clothing (clo)
    # wme, external work, normally around 0 (met)
    
    pa = rh * 10 * exp(16.6536 - 4030.183 / (ta + 235))
    
    icl = 0.155 * clo       #thermal insulation of the clothing in M2K/W
    m = met * 58.15         #metabolic rate in W/M2
    w = wme * 58.15         #external work in W/M2
    mw = m - w              #internal heat production in the human body
    if icl <= 0.078:
        fcl = 1 + (1.29 * icl)
    else:
        fcl = 1.05 + (0.645 * icl)
    
    #heat transf. coeff. by forced convection
    hcf = 12.1 * sqrt(vel) 
    taa = float(ta + 273)
    tra = float(tr + 273)
    tcla = taa + (35.5 - ta) / (3.5 * icl + 0.1) 
    
    p1 = icl * fcl
    p2 = p1 * 3.96
    p3 = p1 * 100
    p4 = p1 * taa
    p5 = 308.7 - 0.028 * mw + p2 * pow(tra/100,4) 
    xn = tcla / 100
    xf = tcla / 50
    eps = 0.00015 
    
    n = 0
    while (abs(xn - xf)) > eps:
        xf = (xf + xn) / 2
        hcn = 2.38 * pow(abs(100.0 * xf - taa),0.25)
        if hcf > hcn:
            hc = hcf
        else:
            hc = hcn
        xn = (p5 + p4 * hc - p2 * pow(xf,4)) / (100 + p3 * hc)
        n+=1
        if n > 150:
            return 1
      
    
    tcl = 100 * xn - 273
    
    # heat loss diff. through skin 
    hl1 = 3.05 * 0.001 * (5733 - (6.99 * mw) - pa) 
    # heat loss by sweating
    if mw > 58.15:
        hl2 = 0.42 * (mw - 58.15)
    else:
        hl2 = 0 
    # latent respiration heat loss 
    hl3 = 1.7 * 0.00001 * m * (5867 - pa) 
    # dry respiration heat loss
    hl4 = 0.0014 * m * (34 - ta)
    # heat loss by radiation  
    hl5 = 3.96 * fcl * (pow(xn,4) - pow(tra/100,4)) 
    # heat loss by convection
    hl6 = fcl * hc * (tcl - ta)
    
    ts = 0.303 * exp(-0.036*m) + 0.028 
    pmv = ts * (mw - hl1 - hl2 - hl3 - hl4 - hl5 - hl6)  
    ppd = 100.0 - 95.0 * exp(-0.03353 * pow(pmv,4.0) - 0.2179 * pow(pmv,2.0))
    
    return [pmv,ppd]

def FtoC(x):
    return (x - 32) * 5 / 9
def CtoF(x):
    return (x * 9 / 5) + 32

def determineColor(val, type, project_obj=False, conditions=False):
    if type == 'tc':
        if val <= 10:
            color = 'green'
        elif val <= 30:
            color = 'yellow'
        else:
            color = 'red'
    elif type == 'tc_top':
        if val < float(project_obj.occupied_heating_sp):
            color = 'blue'
        elif val <= float(project_obj.occupied_cooling_sp):
            color = 'green'
        else:
            color = 'red'
    elif type == 'map_lighting':
        if val >= float(conditions):
            color = 'green'
        elif val >= float(conditions) - 0.1*float(conditions):
            color = 'yellow'
        else:
            color = 'red'
    elif type == 'map_acoustics':
        if val > float(conditions[0]):
            color = 'red'
        elif val >= float(conditions[1]):
            color = 'yellow'
        else:
            color = 'green'
    elif type == 'map_iaq':
        if val > float(conditions):
            color = 'red'
        elif val >= float(conditions) - 200:
            color = 'yellow'
        else:
            color = 'green'
    elif type == 'plenum_map':
        if val <= 55:
            color = colors.rgb2hex(pylab.cm.jet(0)[:3])
        elif val >= 75:
            color = colors.rgb2hex(pylab.cm.jet(1)[:3])
        else:
            color = colors.rgb2hex(pylab.cm.jet((val-55)*0.05)[:3])
    else: #score
        if val <= 25:
            color = 'red'
        elif val <= 50:
            color = 'orange'
        elif val <= 75:
            color = 'yellow'
        else:
            color = 'green'
    return color